package packing;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Scanner;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


/**
 * Given an exam id, gather everything related to the exam. This includes:
 * student source file, solution source file, exam instruction, jar instruction,
 * grade sheet. Before an exam is packaged, the tmp directory is deleted to
 * clear out old exam. In Linux its /tmp/ape; in window its C:\tmp\ape.
 * 
 */
public class Packer
{
	static final String STUDENT_PATH = "/tmp/ape/Student/";
	static final String SOLUTION_PATH = "/tmp/ape/Solution/";
	private Connection conn;
	private Documentation docs;

	/**
	 * Constructor with new connection to database
	 * 
	 * @param c
	 *            The database connection
	 */
	public Packer(Connection c)
	{
		conn = c;
		docs = new Documentation(conn);
	}

	/**
	 * Package the exam with the given id. First, the /tmp/ape on Linux or
	 * C:\tmp\ape on Window is cleared. Second, all file related to the exam is
	 * pulled from database. Third, everything is combined together into a zip
	 * file.
	 * 
	 * @param examID
	 *            The exam id to package
	 */
	public void pack(int examID)
	{
		try
		{
			Packer.deleteDir(new File("/tmp/ape"));// Clear out temp directory
			createExam(examID);// Generate an exam question

			// Archive all file for exam
			ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(
					"/tmp/ape/ape.zip"));
			File sol = new File("/tmp/ape/Solution");
			File stu = new File("/tmp/ape/Student");
			Packer.createZip(sol, sol.getPath(), "Solution", zos);
			Packer.createZip(stu, stu.getPath(), "Student", zos);
			zos.close();
		} catch (IOException io)
		{
			io.printStackTrace();
		}
	}

	/**
	 * Grab all the file need for each questionID from the list
	 * 
	 * @param examID
	 *            The id for the new exam
	 */
	public void createExam(int examID) throws IOException
	{
		try
		{
			Exam exam = new Exam(conn, examID);

			int i = 1;
			while (exam.hasNext())
			{
				String directory = "Part" + (i++);
				new File(STUDENT_PATH + directory).mkdirs();
				new File(SOLUTION_PATH + directory).mkdirs();
				generateQuestion(exam.next(), directory);
			}
			// get date of exam to pass to instruction
			String date = exam.dateScheduled();

			docs.generateJar();
			exam.reset();
			docs.generateInstruction(exam, date);
			exam.reset();
			docs.generateGradesheet(exam, date);

		} catch (SQLException sqlError)
		{
			sqlError.printStackTrace();
		}
	}

	/**
	 * Grab all the file need to generate a question. Eventually it will accept
	 * a question_id to generate more different question.
	 * 
	 * @param question
	 *            A Question object with all information related to a question
	 * @param pDirectory
	 *            The directory name where the file belong
	 */
	public void generateQuestion(Question question, String pDirectory)
			throws IOException, FileNotFoundException, SQLException
	{
		FileOutputStream sStream;
		SourceFile f;

		question.setIterator(Question.SOLUTION);
		while (question.hasNext())
		{
			f = question.next();
			sStream = new FileOutputStream(new File(SOLUTION_PATH + pDirectory,
					f.name));
			// Write out the length of blob to file
			sStream.write(f.getByteArray(), 0, f.length());
			sStream.flush();
			sStream.close();
		}

		// TODO: Construct a class that compiles a program given a Question.
		// The class should be able to run the program and obtain output
		// and methods that allow a .class file to be moved to a specific
		// location.
		//
		// Note this has to be created before the statement below to setIterator

		question.setIterator(Question.PROBLEM);

		while (question.hasNext())
		{
			f = question.next();

			// if it is i/o just output the file
			if (KinID.provided_io(f.kin))
			{
				sStream = new FileOutputStream(new File(STUDENT_PATH
						+ pDirectory, f.name));
				// Write out the length of blob to file
				sStream.write(f.getByteArray(), 0, f.length());
				sStream.flush();
				sStream.close();
			}
			// Check if the file is provided as a .java
			else if (KinID.source_file(f.kin))
			{
				String curLine = null;
				int index = 0;
				Scanner scan = new Scanner(f.file.getBinaryStream());

				PrintWriter studentFile = new PrintWriter(new File(STUDENT_PATH
						+ pDirectory, f.name));
				while (scan.hasNext())
				{
					curLine = scan.nextLine();
					if ((index = curLine.indexOf("/*ans bl*/")) > -1)
					{
						while (!curLine.contains("/*ans ab*/"))
						{
							curLine = scan.nextLine();
						}
						int index2 = curLine.indexOf("/*ans ab*/") + 10;
						String temp = curLine.substring(0, index);
						String temp1 = curLine.substring(index2, curLine
								.length());
						studentFile.printf(temp);
						studentFile.println(temp1);
					} else
					{
						studentFile.println(curLine);
					}
				}
				studentFile.close();
				// Class files must be compiled from source.
			} else if (KinID.class_file(f.kin))
			{
				sStream = new FileOutputStream(new File("/tmp/ape", f.name));
				// Write out the length of blob to file
				sStream.write(f.getByteArray(), 0, f.length());
				sStream.flush();
				sStream.close();

				Compile.generateClassFile(f.name, STUDENT_PATH + pDirectory);
			}
		}
	}

	/**
	 * Create a zip file with the content of the directory passed in.
	 * 
	 * @param dir
	 *            The directory to be zip
	 * @param path
	 *            The absolute path of the file or directory
	 * @param folderInsert
	 *            The folder name inside the zip file
	 * @param zos
	 *            A ZipOutputStream where files will be written
	 * @see "http://www.devx.com/tips/Tip/14049"
	 */
	public static void createZip(File dir, String path, String folderInsert,
			ZipOutputStream zos)
	{
		String[] dirList = null;
		byte[] buffer = new byte[1000];
		int bytesIn = 0;
		try
		{

			if (dir.isDirectory())
			{
				dirList = dir.list();
				for (int i = 0; i < dirList.length; i++)
				{
					File f = new File(path, dirList[i]);
					String tmp = folderInsert + "/" + dirList[i];
					createZip(f, f.getPath(), tmp, zos);
				}
			} else
			{
				FileInputStream fis = new FileInputStream(dir);
				ZipEntry entry = new ZipEntry(folderInsert);
				zos.putNextEntry(entry);
				while ((bytesIn = fis.read(buffer)) != -1)
				{
					zos.write(buffer, 0, bytesIn);
				}
				fis.close();
			}
		} catch (Exception e)
		{
			e.printStackTrace();
		}

	}

	/**
	 * Recursively delete the content of a given path
	 * 
	 * @param path
	 *            The directory to delete
	 * @return Return whether the delete was successful or not
	 * 
	 * @see "http://www.rgagnon.com/javadetails/java-0483.html"
	 */
	public static boolean deleteDir(File path)
	{
		if (path.exists())
		{
			File[] files = path.listFiles();
			for (int i = 0; i < files.length; i++)
			{
				if (files[i].isDirectory())
					deleteDir(files[i]);
				else
					files[i].delete();
			}
			return path.delete();
		}
		return false;
	}

}
